#include "stdafx.hpp"
#include "ColligereDatabaseHandlerBase.hpp"
#include "ColligereVersion.hpp"
#include "ColligereApp.hpp"
#include "ColligereDatabaseError.hpp"

bool col::DatabaseHandlerBase::msEnableSqlDebugLog = false;

wxString col::DatabaseHandlerBase::msMetadataTableName = "metadata";

col::DatabaseHandlerBase::DatabaseHandlerBase()
{
	static wxUint32 id_counter = 0;
	mId = ++id_counter;
}

col::DatabaseHandlerBase::~DatabaseHandlerBase()
{
	if ( wxFileExists ( mBufferFile ) )
	{
		wxRemoveFile ( mBufferFile );
	}
}

void col::DatabaseHandlerBase::Open ( const wxString &rInputFile,
									 const wxString &rSyncType, bool autoVacuum )
{
	using namespace boost;

	PostDatabaseEvent ( COLLIGERE_EVT_DATABASE_OPEN_START );
	mInputFile = rInputFile;

	mBufferFile = wxFileName::CreateTempFileName ( wxGetApp().GetTempDir() );
	wxASSERT ( !mBufferFile.empty() );

	wxLogDebug ( "Database Input File: %s", rInputFile );
	wxLogDebug ( "Database Buffer File: %s", mBufferFile );

	try
	{
		if ( wxFileExists ( rInputFile ) && !decompress_file ( rInputFile, mBufferFile ) )
		{
			throw DatabaseError ( _("Unable to uncompress the file."), rInputFile );
		}
		mDatabaseLayer.Open ( mBufferFile );

		RunQuery ( str ( format ( "PRAGMA synchronous = %1%; "\
			"PRAGMA temp_store = MEMORY; PRAGMA auto_vacuum = %2%") % 
			rSyncType % ( autoVacuum ? "1" : "0" ) ) );
	}
	catch ( wxSQLite3Exception &rErr )
	{	
		mInputFile.clear();
		throw DatabaseError ( rErr.GetMessage(), rInputFile );
	}
	PostDatabaseEvent ( COLLIGERE_EVT_DATABASE_OPEN_END );
}

void col::DatabaseHandlerBase::Close()
{
	mDatabaseLayer.Close();

	wxRemoveFile ( mBufferFile );

	mInputFile.clear();
	mBufferFile.clear();
}

void col::DatabaseHandlerBase::PostDatabaseEvent ( wxEventType evtType )
{
	// wxLogDebug ( "Posting database event (%d)", evtType );

	DatabaseEventBase e ( evtType, mId );
	mpLayoutParent->ProcessEvent ( e ); 
}

void col::DatabaseHandlerBase::SetLayoutParent ( wxWindow *pParent )
{
	wxASSERT ( pParent );
	mpLayoutParent = pParent;
}

void col::DatabaseHandlerBase::RunQuery ( const wxString &rQuery )
{
	if ( !rQuery.empty() )
	{
		if ( msEnableSqlDebugLog ) wxLogDebug ( "SQL: %s", rQuery );
		try
		{
			wxStopWatch sw;
			mDatabaseLayer.ExecuteUpdate ( rQuery );
			if ( msEnableSqlDebugLog ) 
				wxLogGeneric ( LogPerformance, _("Executing took %ldms."), sw.Time() );
		}
		catch ( wxSQLite3Exception &rE )
		{
			throw DatabaseError ( rE.GetMessage(), mInputFile, rQuery );
		}
	}
}

const wxSQLite3ResultSet col::DatabaseHandlerBase::RunQueryWithResults 
( const wxString &rQuery )
{
	if ( !rQuery.empty() )
	{
		if ( msEnableSqlDebugLog ) wxLogDebug ( "SQL: %s", rQuery );
		try
		{
			wxStopWatch sw;
			const wxSQLite3ResultSet results = mDatabaseLayer.ExecuteQuery ( rQuery );
			if ( msEnableSqlDebugLog ) 
				wxLogGeneric ( LogPerformance, "Executing took %ldms.", sw.Time() );
			return results;
		}
		catch ( wxSQLite3Exception &rE )
		{
			throw DatabaseError ( rE.GetMessage(), mInputFile, rQuery );
		}
	}
	return wxSQLite3ResultSet();
}

void col::DatabaseHandlerBase::InsertMetadata()
{
	RunQuery ( boost::str ( boost::format 
		( "INSERT INTO %1% VALUES ( '%2%', '%3%', %4% );") % 
		msMetadataTableName % VERSION_NUMBER % 
		VERSION_NUMBER % GetTypeId() ) );
}

void col::DatabaseHandlerBase::UpdateMetadata()
{
	RunQuery ( boost::str ( boost::format ( "UPDATE %1% SET "\
		"app_version_update = '%2%'") % msMetadataTableName % 
		VERSION_NUMBER ) );
}

bool col::DatabaseHandlerBase::IsQueryable()
{
	return GetItemCount() > 0 || IsTableStructureCreated();
}

bool col::DatabaseHandlerBase::IsTableStructureCreated()
{
	return mDatabaseLayer.TableExists ( msMetadataTableName );
}

void col::DatabaseHandlerBase::CreateTables()
{
	typedef col::DatabaseTable::Column c;
	typedef c::Type col_t;
	typedef c::Constraint col_const;

	const bool tb_structure_created = IsTableStructureCreated();
	wxASSERT ( !tb_structure_created );

	if ( !tb_structure_created )
	{
		DatabaseTableDeque tables ( DatabaseTableListHandler::Get().GetTableList 
			( GetTypeId() ) );

		wxASSERT ( tables.size() > 0 );

		foreach ( DatabaseTable &table, tables )
		{
			RunQuery ( table.ToSqlCreateTableQuery() );
		}

		// Create the metadata table after all other tables have been
		// created. The metadata table is used to check if the table 
		// structure is created.

		DatabaseTable db_metadata ( msMetadataTableName,
			GetTypeId(), empty_string, true );

		db_metadata += c ( MetadataTable::AppVersionInsert, "app_version_insert",
			col_t::Text, col_const::NotNull | col_const::Unique );
		db_metadata += c ( MetadataTable::AppVersionUpdate, "app_version_update",
			col_t::Text, col_const::NotNull | col_const::Unique );
		db_metadata += c ( MetadataTable::DatabaseTypeId, "type_id",
			col_t::Integer, col_const::NotNull | col_const::Unique );

		RunQuery ( db_metadata.ToSqlCreateTableQuery() );
	}
}

const col::DatabaseItemBaseDeque &col::DatabaseHandlerBase::GetItems() const
{
	return mItemList;
}

const col::DatabaseItemBase *col::DatabaseHandlerBase::FindItem ( wxUint32 id ) const
{
	DatabaseItemBaseDeque::const_iterator it = find_if ( mItemList.begin(), 
		mItemList.end(), std::bind2nd ( db_item_equal_id(), id ) );
	return it != mItemList.end() ? it->get() : 0;
}

col::DatabaseItemBase *col::DatabaseHandlerBase::FindItem ( wxUint32 id )
{
	DatabaseItemBaseDeque::iterator it = find_if ( mItemList.begin(), 
		mItemList.end(), std::bind2nd ( db_item_equal_id(), id ) );
	return it != mItemList.end() ? it->get() : 0;
}

size_t col::DatabaseHandlerBase::GetItemCount() const
{
	return mItemList.size();
}

wxUint32 col::DatabaseHandlerBase::CommitItem ( DatabaseItemBase &rItem )
{
	if ( rItem.GetState().Get() == DatabaseItemState::Flag::None )
	{
		return 0;
	}
	if ( rItem.GetState().IsActive ( DatabaseItemState::Flag::Deleted ) &&
		rItem.GetDatabaseId() != 0 )
	{
		/*
		wxLogDebug ( "%s's (%d) state is DELETED, removing from database.",
			rItem.GetTableDescriptor().GetItemName().c_str(), rItem.GetId() );
		*/
		RunQuery ( rItem.ToSqlDeleteQuery() );
		rItem.GetState() = DatabaseItemState::Flag::Deleted;
	}
	else 
	{
		if ( rItem.GetState().IsActive ( DatabaseItemState::Flag::Added ) )
		{
			/*
			wxLogDebug ( "%s's (%d) state is ADDED, adding to database.",
				rItem.GetTableDescriptor().GetItemName().c_str(), rItem.GetId() );
			*/

			// If the state is ADDED then we must NOT have a valid database ID yet.
			wxASSERT ( rItem.GetDatabaseId() == DatabaseItemBase::InvalidItemId );

			RunQuery ( rItem.ToSqlInsertQuery() );

			rItem.SetDatabaseId ( mDatabaseLayer.GetLastRowId().ToLong(), true );
		}
		else if ( rItem.GetState().IsActive ( DatabaseItemState::Flag::Changed ) )
		{
			/*
			wxLogDebug ( "%s's (%d) state is CHANGED, updating changes to database.",
				rItem.GetTableDescriptor().GetItemName().c_str(), rItem.GetId() );
			*/
			RunQuery ( rItem.ToSqlUpdateQuery() );
		}
		rItem.Reset ( DatabaseItemBase::ResetFlags::State |
			DatabaseItemBase::ResetFlags::ColumnUpdateHistory );
	}
	return rItem.GetDatabaseId();
}

void col::DatabaseHandlerBase::Save ( const wxString &rToFile, wxUint32 compressLevel )
{
	if ( !rToFile.empty() && rToFile != mInputFile )
	{
		mInputFile = rToFile;
	}
	CommitChanges();

	if ( !compress_file ( mBufferFile, mInputFile, compressLevel ) )
	{
		throw DatabaseError ( _("Unable to compress the file."), mInputFile );
	}
}

wxUint32 col::DatabaseHandlerBase::GetRowCount ( const DatabaseTable &rTable )
{
	using namespace boost;

	const DatabaseTable::Column &id_col = rTable.GetIdColumn();

	wxSQLite3ResultSet result ( RunQueryWithResults ( str ( format 
		("SELECT COUNT(%1%) FROM %2%;") % id_col.GetName() % rTable.GetName() ) ) );

	while ( result.NextRow() ) { return result.GetInt ( 0, 0 ); }

	return 0;
}